//==================================================================
//  Copyright (C) 2006-2007  Davide Pasca
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//==================================================================

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <malloc.h>

//==================================================================
// on PC the WIN32 constant is defined in the project, also we give 
// priority to windows.h because it will conflict with the definition of
// u_long
#if defined(WIN32)
	#include <windows.h>
	#include "psys.h"
//	#include <gl/glew.h>
//	#include <gl/glu.h>
#else
	#error	"Platform dependent stuff is missing."
#endif

#define ALIGN	64	// must be at least 12 !!

#define INTERNAL_MANAGER
//#define TRACK_ACTIVITY

//==================================================================
#ifdef TRACK_ACTIVITY
	#define	MEM_ACTIVITY_PRINTF	PDEBUG_PRINTF
#else
	#define	MEM_ACTIVITY_PRINTF
#endif

//==================================================================
#if defined(_WINBASE_)
static LARGE_INTEGER	_counter_frequency;
static int				_use_performance_counter;
static int				_timer_initialized;
#endif
static FILE				*_log_filep = NULL;

//==================================================================
namespace PSYS {

//==================================================================
void Initialize( const TCHAR *log_fnamep )
{
static int	initialized;

	if ( initialized )
	{
		PASSERT( 0 );
		return;
	}
	initialized = 1;

#if defined(_WINBASE_)	// supposedly should work for both PC and Xenon
	_counter_frequency.QuadPart = 0;
	if ( QueryPerformanceFrequency(  &_counter_frequency ) && _counter_frequency.QuadPart )
		_use_performance_counter = 1;
	else
		_use_performance_counter = 0;
#endif

	if ( log_fnamep )
	{
		_tfopen_s( &_log_filep, log_fnamep, _T("w") );
	}
}

//==================================================================
u_long64 TimerGetUL64(void)
{
#if defined(_WINBASE_)	// supposedly should work for both PC and Xenon
LARGE_INTEGER	qticks;

	if ( !_use_performance_counter || !QueryPerformanceCounter( &qticks ) )
	{
		PASSERT( 0 );
		return 0;
	}

	return qticks.QuadPart * 1000 / _counter_frequency.QuadPart;
#else
	PASSERT( 0 );
	return 0;
#endif
}

//==================================================================
double TimerGetD(void)
{
#if defined(_WINBASE_)	// supposedly should work for both PC and Xenon
LARGE_INTEGER	qticks;

	if ( !_use_performance_counter || !QueryPerformanceCounter( &qticks ) )
	{
		PASSERT( 0 );
		return 0;
	}

	return qticks.QuadPart * 1000.0 / _counter_frequency.QuadPart;
#else
	PASSERT( 0 );
	return 0;
#endif
}

//==================================================================
// Warning: doesn't take into account the program data size, and mem wasted in alignment !
#ifdef INTERNAL_MANAGER
static int	_allocated_mem = 0;
#endif
static int	_allocated_blocks = 0;	// current number of allocated memory blocks (increase on malloc(), decrease on free())

//==================================================================
void psys_mem_info_get( psys_mem_info_t *mip )
{
	mip->total_allocated = _allocated_mem;
	mip->n_allocated_blocks = _allocated_blocks;
}

//==================================================================
u_int psys_mem_allocated(void)
{
#ifdef INTERNAL_MANAGER
	return _allocated_mem;
#else
	return 0;
#endif
}

//==================================================================
int Assert( int ok, const TCHAR *srcfnamep, int line )
{
	if NOT( ok )
		DebugPrintF( _T(" Assert failed: %s (%i)\n"), srcfnamep, line );

	return ok;
}

//==================================================================
int AssertMsg( int ok, const TCHAR *msgp, const TCHAR *srcfnamep, int line )
{
	if NOT( ok )
	{
		DebugPrintF( _T("Assert: %s - %s (%i)\n"), msgp, srcfnamep, line );
	}

	return ok;
}

//==================================================================
// allocate extra ALIGN-sized block, put the size at the begin of
// the block and return offset pointer
//
void *Malloc( u_int size )
{
	u_char	*p;

#ifdef INTERNAL_MANAGER
	
	int		padding;

	p = (u_char *)malloc( size + ALIGN * 2 );

	if ( p == NULL )
		return NULL;

	padding = (((u_int)p + (ALIGN-1)) & ~(ALIGN-1)) - (u_int)p;
	PASSERT( padding <= 255 );

	p += padding;
	((unsigned int *)p)[0] = 0xbeefbeef;
	((unsigned int *)p)[1] = size;
	p[8] = padding;
	p[9] = padding;
	p[10] = padding;
	p[11] = padding;

	p = p + ALIGN;

	_allocated_mem += size + ALIGN * 2;
#else
	p = malloc( size );
	PASSERT( p != NULL );
#endif

	_allocated_blocks += 1;

	return p;
}

//==================================================================
void *Realloc( void *p, u_int size )
{
	u_char	*new_p;

#ifdef INTERNAL_MANAGER
	u_int	old_size;
	u_char	*base_p;
	u_char	*sys_p;

	if ( !p )
		return Malloc( size );

	base_p = (u_char *)p - ALIGN;
	if ( ((u_int *)base_p)[0] != 0xbeefbeef )
	{
		PDEBUG_PRINTF( _T("Realloc: very bad things happening !!!\n") );
		PASSERT( 0 );
		return NULL;
	}
	old_size = ((u_int *)base_p)[1];
	if ( old_size == size )
		return p;

	sys_p = base_p - base_p[8];

	if NOT( new_p = (u_char *)Malloc( size ) )
	{
		_allocated_mem -= old_size + ALIGN * 2;	// _allocated_mem update bug found by Karasawa-san 
		free( sys_p );							// (remember to match Free() behaviour !!)
		_allocated_blocks -= 1;
		PASSERT( new_p != NULL );
		return NULL;
	}
	memcpy( new_p, p, old_size < size ? old_size : size );

	_allocated_mem -= old_size + ALIGN * 2;	// _allocated_mem update bug found by Karasawa-san 
	free( sys_p );							// (remember to match Free() behaviour !!)
	_allocated_blocks -= 1;
#else
	new_p = realloc( p, size );
	PASSERT( new_p != NULL );
#endif

	if NOT( new_p != NULL )
		return NULL;

	PASSERT( new_p != NULL );
	return new_p;
}

//==================================================================
void *Realloc2( void **pp, u_int size )
{
void	*newp;

	if ( newp = Realloc( *pp, size ) )
		*pp = newp;

	return newp;
}

//==================================================================
void *psys_cmalloc( u_int size )
{
void	*p;

	p = Malloc( size );
	if ( p == NULL )
		return NULL;

	memset( p, 0, size );

	return p;
}

//==================================================================
void _Free( void *p )
{
	if ( !p )
		DebugPrintF( _T("Free: invalid parameter\n") );

#ifdef INTERNAL_MANAGER
	{
		u_char	*base_p;
		u_char	*sys_p;

		base_p = (u_char *)p - ALIGN;
		sys_p = base_p - base_p[8];

		_allocated_mem -= ((u_int *)base_p)[1] + ALIGN;
		_allocated_blocks -= 1;

		free( sys_p );
	}
#else
	_allocated_blocks -= 1;
	free( p );
#endif
}

//==================================================================
void Free( void *p )
{
	_Free( p );
}

//==================================================================
void *Malloc_dbg( u_int size, const char *filep, int line )
{
void	*p = Malloc( size );

	MEM_ACTIVITY_PRINTF( ">>(%3i) 0x%08x - %4i --- %4i, %s\n", _allocated_blocks, p, size, line, filep );
	return p;
}

//==================================================================
void *Realloc_dbg( void *p, u_int size, const char *filep, int line )
{
void	*newp = Realloc( p, size );

	MEM_ACTIVITY_PRINTF( ">>(%3i) 0x%08x - %4i --- %4i, %s\n", _allocated_blocks, newp, size, line, filep );	// old_size
	return newp;
}

//==================================================================
void *Realloc2_dbg( void **pp, u_int size, const char *filep, int line )
{
void	*newp = Realloc2( pp, size );

	MEM_ACTIVITY_PRINTF( ">>(%3i) 0x%08x - %4i --- %4i, %s\n", _allocated_blocks, newp, size, line, filep );	// old_size
	return newp;
}

//==================================================================
void *Calloc_dbg( u_int size, const char *filep, int line )
{
void	*p = psys_cmalloc( size );

	MEM_ACTIVITY_PRINTF( ">>(%3i) 0x%08x - %4i --- %4i, %s\n", _allocated_blocks, p, size, line, filep );
	return p;
}

//==================================================================
void _Free_dbg( void *p, const char *filep, int line )
{
	#ifdef TRACK_ACTIVITY
		#ifdef INTERNAL_MANAGER
			void			*sys_p = ((char *)p - ALIGN);
			MEM_ACTIVITY_PRINTF( "<<(%3i) 0x%08x - %4i --- %4i, %s\n", _allocated_blocks, p, ((u_int *)sys_p)[1], line, filep );
		#else
			MEM_ACTIVITY_PRINTF( "<<(%3i) 0x%08x - **** --- %4i, %s\n", _allocated_blocks, p, line, filep );
		#endif
	#endif

	_Free( p );
}

//==================================================================
void Free_dbg( void *p, const char *filep, int line )
{
	#ifdef TRACK_ACTIVITY
		#ifdef INTERNAL_MANAGER
			void			*sys_p = ((char *)p - ALIGN);
			MEM_ACTIVITY_PRINTF( "<<(%3i) 0x%08x - %4i --- %4i, %s\n", _allocated_blocks, p, ((u_int *)sys_p)[1], line, filep );
		#else
			MEM_ACTIVITY_PRINTF( "<<(%3i) 0x%08x - **** --- %4i, %s\n", _allocated_blocks, p, line, filep );
		#endif
	#endif

	Free( p );
}

//==================================================================
void DebugPrintF( const TCHAR *fmt, ... )
{
	va_list	va;

	va_start( va, fmt );

#if defined(WIN32)
	{
		static TCHAR	buff[4096];

		_vstprintf_s( buff, fmt, va );
		OutputDebugString( buff );

		if ( _log_filep )
		{
			_fputts( buff, _log_filep );
		}
	}

#else
	#error	"Platform dependent stuff is missing."
#endif

	va_end( va );
}

//==================================================================
void MsgPrintf( const TCHAR *fnamep, int line, const TCHAR *fmtp, ... )
{
	TCHAR	buff[2048], caption[1024];
	va_list	vlist;

	va_start( vlist, fmtp );
		_vstprintf_s( buff, fmtp, vlist );
	va_end( vlist );

	if ( line >= 0 )
		_stprintf_s( caption, _T("%s : %ld"), fnamep, line );
	else
		_stprintf_s( caption, _T("%s"), fnamep );


#if defined(WIN32)
	MessageBox( GetDesktopWindow(), buff, caption, MB_OK );

#else
	#error	"Platform dependent stuff is missing."
#endif
}

//==================================================================
void psys_array_hintexpand_n( void **pp, int n, int *nalloc, int elem_size, int n_new_elems )	// no exception, it's only an hint !
{
void	*p;
int		n_try_alloc;
int		new_min_elems;
int		start_idx;

	new_min_elems = n + n_new_elems;
	if ( new_min_elems > *nalloc )
	{
		if ( new_min_elems <= 1 && elem_size < 128 )
			n_try_alloc = 4;
		else
		{
			n_try_alloc = *nalloc * 2;
			if ( n_try_alloc < new_min_elems )
				n_try_alloc = new_min_elems;
		}

		if NOT( p = (void *)PSYS_REALLOC( *pp, n_try_alloc * elem_size ) )
			return;// NULL;

		*nalloc = n_try_alloc;
		*pp = p;
	}
	start_idx = n;

	//return (u_char *)*pp + start_idx * elem_size;
}

//==================================================================
static int (*_fread_callback)( void *, size_t, size_t, FILE * ) = (int (*)( void *, size_t, size_t, FILE * ))fread;

//==================================================================
int	psys_fread( void *destp, size_t size, size_t count, FILE *fp )
{
	return _fread_callback( destp, size, count, fp );
}

//==================================================================
void psys_replace_fread( int (*user_defined_fread)( void *, size_t, size_t, FILE * ) )
{
	_fread_callback = user_defined_fread;
}

//==================================================================
TCHAR *TFGets_UTF8( FILE *fp, TCHAR *desp, u_int des_max_len )
{
	char	utf8_buff[4096];

	if NOT( fgets( utf8_buff, sizeof(utf8_buff), fp ) )
		return NULL;

	if ( MultiByteToWideChar( CP_UTF8, 0, utf8_buff, -1, desp, des_max_len ) == 0 )
		return NULL;

	return desp;
}

//==================================================================
int FTPrintF_S_UTF8( FILE *stream, const TCHAR *format, ... )
{
	va_list	va;
	va_start( va, format );
		int retval = VFTPrintF_S_UTF8( stream, format, va );
	va_end( va );

	return retval;
}

//==================================================================
int VFTPrintF_S_UTF8( FILE *stream, const TCHAR *format, va_list va )
{
	TCHAR	buff[4096];
	char	utf8_buff[4096];

	_vstprintf_s( buff, format, va );

	size_t	buff_len = _tcslen(buff);
	if ( buff_len )
	{
		int utf8_len = WideCharToMultiByte( CP_UTF8, 0, buff, buff_len,
									utf8_buff, _countof(utf8_buff),
									NULL, 0 );
		if NOT( utf8_len )
			return -1;

		return fwrite( utf8_buff, sizeof(char), utf8_len, stream );
	}
	else
	{
		return 0;	// nothing to write
	}
}

//==================================================================
};
